"""
Simple backtester for the multi-factor MACD strategy.
Produces trade log, equity curve and metrics.
"""
from typing import Dict, Any, List
import pandas as pd
import numpy as np
from .strategy import compute_indicators, generate_signals, position_size, apply_transaction_cost


def run_backtest(df: pd.DataFrame, params: Dict[str, Any], initial_capital: float = 1_000_000.0) -> Dict[str, Any]:
    df = compute_indicators(df, params)
    df = generate_signals(df, params)

    cash = initial_capital
    position = 0
    entry_price = 0.0
    equity_curve = []
    trades: List[Dict[str, Any]] = []

    for i, row in df.iterrows():
        price = row['收盘']
        date = row['日期']
        vol = row.get('成交量', 1)
        atr = row.get('atr', 1.0)
        sig = row['signal']

        # check exit by stop/target
        if position != 0:
            # dynamic stop/target
            stop_mult = params.get('stop_atr_mult', 2.0)
            take_mult = params.get('take_atr_mult', 4.0)
            if position > 0:
                stop_price = entry_price - stop_mult * atr
                take_price = entry_price + take_mult * atr
                if price <= stop_price or price >= take_price:
                    # close
                    eff_price, cost = apply_transaction_cost(price, abs(position), 'sell', vol, params)
                    cash += eff_price * position - cost
                    trades.append({'date': date, 'type': 'exit', 'price': eff_price, 'size': position, 'cost': cost})
                    position = 0
            else:
                stop_price = entry_price + stop_mult * atr
                take_price = entry_price - take_mult * atr
                if price >= stop_price or price <= take_price:
                    eff_price, cost = apply_transaction_cost(price, abs(position), 'buy', vol, params)
                    cash += eff_price * position - cost
                    trades.append({'date': date, 'type': 'exit', 'price': eff_price, 'size': position, 'cost': cost})
                    position = 0

        # new entry
        if sig == 1 and position == 0:
            size = position_size(cash, price, atr, params)
            if size > 0:
                eff_price, cost = apply_transaction_cost(price, size, 'buy', vol, params)
                cash -= eff_price * size + cost
                position = size
                entry_price = eff_price
                trades.append({'date': date, 'type': 'entry', 'price': eff_price, 'size': size, 'cost': cost})
        elif sig == -1 and position == 0:
            # allow short? For now, skip shorting; could be extended
            pass

        # equity
        market_val = position * price
        total = cash + market_val
        equity_curve.append({'date': date, 'equity': total})

    eq_df = pd.DataFrame(equity_curve)
    trades_df = pd.DataFrame(trades)

    results = {'equity_curve': eq_df, 'trades': trades_df, 'final_cash': cash}
    return results


def compute_metrics(eq_df: pd.DataFrame, trades_df: pd.DataFrame, initial_capital: float) -> Dict[str, Any]:
    eq_df = eq_df.copy()
    eq_df['date'] = pd.to_datetime(eq_df['date'])
    eq_df = eq_df.sort_values('date').reset_index(drop=True)
    eq_df['returns'] = eq_df['equity'].pct_change().fillna(0)
    total_return = eq_df['equity'].iloc[-1] / initial_capital - 1
    days = (eq_df['date'].iloc[-1] - eq_df['date'].iloc[0]).days + 1
    annualized = (1 + total_return) ** (365.0 / max(days, 1)) - 1
    sharpe = (eq_df['returns'].mean() / (eq_df['returns'].std() + 1e-9)) * np.sqrt(252)

    # max drawdown
    cum_max = eq_df['equity'].cummax()
    drawdown = (eq_df['equity'] - cum_max) / cum_max
    maxdd = drawdown.min()

    # trades stats
    win = 0
    loss = 0
    pnl_list = []
    if not trades_df.empty:
        # pair entries and exits
        stack = []
        for _, t in trades_df.iterrows():
            if t['type'] == 'entry':
                stack.append(t)
            elif t['type'] == 'exit' and len(stack) > 0:
                e = stack.pop(0)
                pnl = (t['price'] - e['price']) * t['size'] - (t['cost'] + e['cost'])
                pnl_list.append(pnl)
                if pnl > 0:
                    win += 1
                else:
                    loss += 1

    win_rate = win / (win + loss) if (win + loss) > 0 else np.nan
    avg_win = np.mean([p for p in pnl_list if p > 0]) if any([p > 0 for p in pnl_list]) else 0
    avg_loss = np.mean([p for p in pnl_list if p <= 0]) if any([p <= 0 for p in pnl_list]) else 0
    profit_loss_ratio = (abs(avg_win) / (abs(avg_loss) + 1e-9)) if avg_loss != 0 else np.nan

    metrics = {
        'total_return': total_return,
        'annualized_return': annualized,
        'sharpe': sharpe,
        'max_drawdown': maxdd,
        'win_rate': win_rate,
        'profit_loss_ratio': profit_loss_ratio,
        'n_trades': len(pnl_list)
    }
    return metrics
